package com.hy.controller.ali;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.hy.common.Result.ResponseEnum;
import com.hy.common.Result.Result;
import com.hy.common.exception.StandardException;
import com.hy.entity.AliPayBean;
import com.hy.service.AliPayService;
import com.hy.service.OrderService;
import com.hy.utils.StringUtils;
import com.hy.utils.aliResponse.AliResponseEnum;
import com.hy.utils.aliResponse.AliResponseUtils;
import com.hy.utils.aliResponse.AliTradeStatusUtils;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @作用 支付宝支付控制层
 * @作者 坏银
 * @时间 2022/4/13 18:20
 */
@RestController
@Log4j2
@RequestMapping("api/aliPay")
@Api(tags = "支付宝支付控制层")
public class AliPayController extends AbstractAliPayApiController {

    @Resource
    private AliPayBean aliPayBean;

    @Resource
    private OrderService orderService;
    @Resource
    private AliPayService aliPayService;

    /**
     * 普通公钥模式
     */
    private final static String NOTIFY_URL = "/api/aliPay/notify_url";
    private final static String RETURN_URL = "/api/aliPay/return_url";

    //    /**
//     * 证书模式
//     */
//    private final static String NOTIFY_URL = "/api/aliPay/cert_notify_url";
//    private final static String RETURN_URL = "/api/aliPay/cert_return_url";
    @Override
    public AliPayApiConfig getApiConfig() {
        AliPayApiConfig aliPayApiConfig;
        try {
            aliPayApiConfig = AliPayApiConfigKit.getApiConfig(aliPayBean.getAppId());
        } catch (Exception e) {
            aliPayApiConfig = AliPayApiConfig.builder()
                    .setAppId(aliPayBean.getAppId())
                    .setAliPayPublicKey(aliPayBean.getPublicKey())
                    .setAppCertPath(aliPayBean.getAppCertPath())
                    .setAliPayCertPath(aliPayBean.getAliPayCertPath())
                    .setAliPayRootCertPath(aliPayBean.getAliPayRootCertPath())
                    .setCharset("UTF-8")
                    .setPrivateKey(aliPayBean.getPrivateKey())
                    .setServiceUrl(aliPayBean.getServerUrl())
                    .setSignType("RSA2")
                    // 普通公钥方式
                    .build();

        }
        return aliPayApiConfig;
    }

    @ApiOperation(value = "二维码当面付", notes =
            "1.预创建当面付获取二维码" +
                    "2.订单号和支付金额可选。" +
                    "3.默认支付86元，预留的合法时间5分钟")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", value = "商户订单号"),
            @ApiImplicitParam(name = "totalAmount", value = "支付金额", defaultValue = "86")
    })
    @ResponseBody
    @PostMapping(value = "/tradePreCreatePay")
    public Map tradePreCreatePay(
            @RequestParam(value = "outTradeNo", required = false) Long inOutTradeNo,
            @RequestParam(value = "totalAmount", required = false) String inTotalAmount) {
        //异步通知回调接口
        String notifyUrl = aliPayBean.getDomain() + NOTIFY_URL;

        //构建当面付模型
        AlipayTradePrecreateModel model = aliPayService.constructTradePreCreatePay(inOutTradeNo, inTotalAmount);

        //请求支付宝当面付接口
        Map resultMap;
        try {
            //发起请求
            String resultStr = Optional.ofNullable(AliPayApi.tradePrecreatePayToResponse(model, notifyUrl))
                    .orElseThrow(() -> new StandardException(ResponseEnum.A_LI_FAILED_TO_CREATE_ORDER))
                    .getBody();
            resultMap = AliResponseUtils.getResponseBody(resultStr, AliResponseEnum.TRADE_PRECREATE_RESPONSE.getKey());
        } catch (Exception e) {
            //如果请求失败
            log.error("请求支付宝当面付接口失败");
            throw new StandardException(ResponseEnum.A_LI_FAILED_TO_REQUEST_ALIPAY_BACKEND_INTERFACE);
        }

        /*
          对阿里后端发送请求成功的处理
          1.请求成功
           * 成功创建订单
           * 订单已存在，但未支付
          2，请求失败
           * 请求发送成功但由于其他问题失败
         */
        //对请求成功的处理
        if (AliResponseUtils.judgeSuccess(resultMap)) {
            //保存订单成功
            if (orderService.putAliOrder(Long.valueOf(model.getOutTradeNo()), model.getTotalAmount())) {
                return resultMap;
            } else {
                //保存订单失败，表示已存在订单
                //如果订单支付中
                String orderState = orderService.getOrderState(Long.valueOf(model.getOutTradeNo()));
                if ("订单支付中".equals(orderState)) {
                    resultMap.put("message", "已存在订单");
                    return resultMap;
                } else {
                    //如果订单不是支付状态,抛出异常
                    throw new StandardException(ResponseEnum.ORDER_IS_NOT_IN_PAYMENT_STATUS);
                }
            }
        }

        //对请求成功但是出现其他问题的处理
        String code = (String) resultMap.get("code");
        String sub_msg = (String) resultMap.get("sub_msg");
        throw new StandardException(Integer.valueOf(code), sub_msg);
    }

    @ApiOperation(value = "PC浏览器支付", notes =
            "1.创建pc支付订单" +
                    "2.支付金额默认88.88元")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", value = "创建的订单id"),
            @ApiImplicitParam(name = "totalAmount", value = "支付金额", defaultValue = "88.88")
    })
    @ResponseBody
    @PostMapping(value = "/pcPay")
    public void pcPay(HttpServletResponse response,
                      @RequestParam(value = "outTradeNo", required = false) Long inOutTradeNo,
                      @RequestParam(value = "totalAmount", required = false) String inTotalAmount) {
        //支付成功返回地址
        String returnUrl = aliPayBean.getDomain() + RETURN_URL;
        //支付异步回调地址
        String notifyUrl = aliPayBean.getDomain() + NOTIFY_URL;

        //构建pc支付模型
        AlipayTradePagePayModel model = aliPayService.constructTradePagePayModel(inOutTradeNo, inTotalAmount);

        //创建订单
        orderService.putAliOrder(Long.valueOf(model.getOutTradeNo()), model.getTotalAmount());

        //如果订单支付中
        String orderState = orderService.getOrderState(Long.valueOf(model.getOutTradeNo()));

        //如果订单不是支付状态,抛出异常
        if (!"订单支付中".equals(orderState)) {
            throw new StandardException(ResponseEnum.ORDER_IS_NOT_IN_PAYMENT_STATUS);
        }

        //请求支付宝当面付接口
        try {
            AliPayApi.tradePage(response, model, notifyUrl, returnUrl);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建订单
     *
     * @return {"alipay_trade_create_response":{
     * "code":"10000",
     * "msg":"Success",
     * "out_trade_no":"081014283315033",
     * "trade_no":"2017081021001004200200274066"},
     * "sign":"签名"
     * }
     */
    @ApiOperation(value = "订单创建", notes = "" +
            "1.创建一个支付订单" +
            "2.id可选，不选自动生成")
    @ApiImplicitParam(name = "outTradeNo", value = "商户订单号")
    @ResponseBody
    @PutMapping(value = "/tradeCreate")
    public AlipayTradeCreateResponse tradeCreate(
            @RequestParam(value = "outTradeNo", required = false) Long inoutTradeNo,
            @RequestParam(value = "totalAmount", required = false, defaultValue = "88.88") String inTotalAmount) {
        //支付回调接口
        String notifyUrl = aliPayBean.getDomain() + NOTIFY_URL;

        //构建创建订单模型
        AlipayTradeCreateModel model = aliPayService.constructTradeCreateModel(inoutTradeNo, inTotalAmount);

        //向支付宝后端发起请求
        try {
            AlipayTradeCreateResponse response = AliPayApi.tradeCreateToResponse(model, notifyUrl);
            //TODO 还不知道什么用，暂时不搞，重构响应参数
            return response;
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return null;
    }


    @ApiOperation(value = "订单撤销", notes = "" +
            "1.根据商户订单号或者支付宝订单号撤销订单" +
            "2.已支付退款，未支付取消订单")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", value = "商户订单号"),
            @ApiImplicitParam(name = "tradeNo", value = "支付宝订单号")})
    @ResponseBody
    @DeleteMapping(value = "/tradeCancel")
    public HashMap<Object, Object> tradeCancel(
            @RequestParam(required = false, name = "outTradeNo") String outTradeNo,
            @RequestParam(required = false, name = "tradeNo") String tradeNo) {
        //构建订单撤销模型
        AlipayTradeCancelModel model = aliPayService.constructTradeCancelModel(outTradeNo, tradeNo);

        //向阿里后端发起请求
        Map response;
        try {
            String responseJson = JSON.toJSONString(AliPayApi.tradeCancelToResponse(model));
            response = JSON.parseObject(responseJson, Map.class);

        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new StandardException(ResponseEnum.A_LI_FAILED_TO_REQUEST_ALIPAY_BACKEND_INTERFACE);
        }

        //结果返回处理
        HashMap<Object, Object> result = new HashMap<>(32);

        //如果订单撤销成功
        if (AliResponseUtils.judgeSuccess(response)) {
            result.put("msg", "success");
            //订单标记为撤销状态
            orderService.orderCancel(Long.valueOf(model.getOutTradeNo()));
        } else {
            Result r = AliResponseUtils.getResponseBodyCodeAndMessage(response);
            throw new StandardException(r.getCode(), r.getMessage());
        }
        return result;
    }

    @ApiOperation(value = "订单关闭", notes =
            "1.根据商户订单号或者支付宝订单号取消订单")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", value = "商户订单号"),
            @ApiImplicitParam(name = "tradeNo", value = "支付宝订单号")})
    @ResponseBody
    @DeleteMapping(value = "/tradeClose")
    public Map tradeClose(
            @RequestParam(required = false, name = "outTradeNo") String outTradeNo,
            @RequestParam(required = false, name = "tradeNo") String tradeNo) {
        //构建订单撤销模型
        AlipayTradeCloseModel model = aliPayService.constructTradeCloseModel(outTradeNo, tradeNo);

        //向支付宝发起请求
        Map response;
        try {
            String responseJson = JSON.toJSONString(AliPayApi.tradeCloseToResponse(model));
            response = JSON.parseObject(responseJson, Map.class);
        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new StandardException(ResponseEnum.A_LI_FAILED_TO_REQUEST_ALIPAY_BACKEND_INTERFACE);
        }

        //对结果的处理
        HashMap<Object, Object> result = new HashMap<>();

        //如果订单撤销成功
        if (AliResponseUtils.judgeSuccess(response)) {
            result.put("msg", "success");
            //订单标记为关闭状态
            orderService.orderClosed(Long.valueOf(model.getOutTradeNo()));
        } else {
            Result r = AliResponseUtils.getResponseBodyCodeAndMessage(response);
            throw new StandardException(r.getCode(), r.getMessage());
        }
        return result;
    }


    @ApiOperation(value = "订单交易查询", notes = "" +
            "1.根据商户订单号或支付宝交易号查询交易情况。" +
            "2.需要与支付宝方进行交互才会自动创建订单。" +
            "3.官方文档如下：https://opendocs.alipay.com/support/01rfsg")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", value = "商户订单号"),
            @ApiImplicitParam(name = "tradeNo", value = "支付宝交易号")
    })
    @ResponseBody
    @PostMapping(value = "/tradeQuery")
    public HashMap<String, Object> tradeQuery(
            @RequestParam(required = false, name = "outTradeNo") String outTradeNo,
            @RequestParam(required = false, name = "tradeNo") String tradeNo) {
        //构建订单查询模型
        AlipayTradeQueryModel model = aliPayService.constructTradeQueryModel(outTradeNo, tradeNo);

        //向支付宝发起请求
        Map map;
        try {
            String response = AliPayApi.tradeQueryToResponse(model).getBody();
            map = AliResponseUtils.getResponseBody(response, AliResponseEnum.TRADE_QUERY_RESPONSE.getKey());
        } catch (AlipayApiException e) {
            e.printStackTrace();
            log.error("订单查询失败");
            throw new StandardException(ResponseEnum.A_LI_FAILED_TO_REQUEST_ALIPAY_BACKEND_INTERFACE);
        }

        //对结果的处理

        //如果结果是我们预期的结果
        if (AliResponseUtils.judgeSuccess(map)) {
            HashMap<String, Object> result = new HashMap<>();
            String trade_status = (String) map.get("trade_status");
            String out_trade_no = (String) map.get("out_trade_no");
            result.put("trade_status", AliTradeStatusUtils.getTradeStatusWithChinese(trade_status));
            //如果和数据库不同，异步修改数据库
            orderService.syncOrderStatus(trade_status,out_trade_no);
            return result;
        } else {
            //如果结果不是预期的
            Result r = AliResponseUtils.getResponseBodyCodeAndMessage(map);
            throw new StandardException(r.getCode(), r.getMessage());
        }
    }

    @ApiOperation(value = "订单退款", notes = "根据商户订单号或支付宝订单号对该订单发起退款")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", value = "商户订单号"),
            @ApiImplicitParam(name = "tradeNo", value = "支付宝交易号")
    })
    @ResponseBody
    @DeleteMapping(value = "/tradeRefund")
    public String tradeRefund(
            @RequestParam(required = false, name = "outTradeNo") String outTradeNo,
            @RequestParam(required = false, name = "tradeNo") String tradeNo) {

        try {
            AlipayTradeRefundModel model = new AlipayTradeRefundModel();
            if (StringUtils.isNotEmpty(outTradeNo)) {
                model.setOutTradeNo(outTradeNo);
            }
            if (StringUtils.isNotEmpty(tradeNo)) {
                model.setTradeNo(tradeNo);
            }
            model.setRefundAmount("86.00");
            model.setRefundReason("正常退款");
            return AliPayApi.tradeRefundToResponse(model).getBody();
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return null;
    }

    @ApiOperation(value = "商户余额查询", notes = "根据商户pid查询该账号余额")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "aliPayPId", value = "商户pid", defaultValue = "2088621958345111"),
            @ApiImplicitParam(name = "merchantUserId", value = "商户账号")
    })

    @ResponseBody
    @PostMapping(value = "/accountQuery")
    public Map accountQuery(
            @RequestParam(name = "aliPayPId", required = false) String aliPayPId,
            @RequestParam(name = "merchantUserId", required = false) String merchantUserId) {

        //构建商户余额查询
        AlipayFundAccountQueryModel model = aliPayService.constructFundAccountQueryModel(aliPayPId, merchantUserId);

        //发起请求
        Map map;
        try {
            String response = AliPayApi.accountQueryToResponse(model, null).getBody();
            map = AliResponseUtils.getResponseBody(response, AliResponseEnum.FUND_ACCOUNT_QUERY_RESPONSE.getKey());
        } catch (Exception e) {
            log.error("商户{}的余额查询失败", model.getAlipayUserId());
            throw new StandardException(ResponseEnum.A_LI_FAILED_TO_REQUEST_ALIPAY_BACKEND_INTERFACE);
        }

        //对结果的处理
        //如果是预期的结果
        if (AliResponseUtils.judgeSuccess(map)) {
            map.remove("code");
            map.remove("msg");
            return map;
        } else {
            //如果结果不是预期，拿到错误码和错误消息
            Result r = AliResponseUtils.getResponseBodyCodeAndMessage(map);
            throw new StandardException(r.getCode(), r.getMessage());
        }
    }

    @ApiOperation(value = "异步回调支付结果", notes = "异步通知后端拿到支付结果")
    @ResponseBody
    @PostMapping("notify_url")
    public String notifyUrl(HttpServletRequest request) {
        try {
            //获取支付宝POST过来反馈信息
            Map<String, String> response = AliPayApi.toMap(request);
            //校验并判断是否支付成功
            boolean verifyResult = AlipaySignature.rsaCheckV1(response, aliPayBean.getPublicKey(), "UTF-8", "RSA2");
            //获取支付成功的商户订单号
            String outTradeNo = AliResponseUtils.getNotifyOutTradeNo(response);

            //支付判断
            if (verifyResult) {
                //支付成功回调，注意幂等性问题
                orderService.orderPaid(Long.valueOf(outTradeNo));
                log.info("订单:{} 回调验证成功", outTradeNo);
                return "success";
            } else {
                //支付失败逻辑
                log.error("订单:{} 回调验证失败", outTradeNo);
                return "failure";
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            log.error("支付成功回调出现未知错误");
            return "failure";
        }
    }
}
